package com.sv.web;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URL;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.ToolboxFactory;
import org.apache.velocity.tools.config.XmlFactoryConfiguration;
import org.apache.velocity.tools.view.ViewToolContext;

import org.springframework.beans.BeansException;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.servlet.view.velocity.VelocityLayoutView;

public class VelocityLayoutViewExpand extends
        org.springframework.web.servlet.view.velocity.VelocityToolboxView {

    /**
     * The default {@link #setLayoutUrl(String) layout url}.
     */
    public static final String DEFAULT_LAYOUT_URL = "layout.vm";

    /**
     * The default {@link #setLayoutKey(String) layout key}.
     */
    public static final String DEFAULT_LAYOUT_KEY = "layout";

    /**
     * The default {@link #setScreenContentKey(String) screen content key}.
     */
    public static final String DEFAULT_SCREEN_CONTENT_KEY = "screen_content";

    private String layoutUrl;

    private String layoutDir;

    private String layoutKey = DEFAULT_LAYOUT_KEY;

    private String screenContentKey = DEFAULT_SCREEN_CONTENT_KEY;

    private Resource toolboxConfigResource;

    @Override
    protected void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        this.layoutDir = (String) getVelocityEngine().getProperty(
                "tools.view.servlet.layout.directory");
        this.layoutUrl = (String) getVelocityEngine().getProperty(
                "tools.view.servlet.layout.default.template");
        if (StringUtils.isBlank(this.layoutUrl)) {
            this.layoutUrl = DEFAULT_LAYOUT_URL;
        }
        if (!this.layoutDir.endsWith("/")) {
            this.layoutDir = this.layoutDir + "/";
        }
    }

    public Resource getToolboxConfigResource() {
        return toolboxConfigResource;
    }

    public void setToolboxConfigResource(Resource toolboxConfigResource) {
        this.toolboxConfigResource = toolboxConfigResource;
    }

    /**
     * Set the layout template to use. Default is {@link #DEFAULT_LAYOUT_URL
     * "layout.vm"}.
     * 
     * @param layoutUrl
     *            the template location (relative to the template root
     *            directory)
     */
    public void setLayoutUrl(String layoutUrl) {
        this.layoutUrl = layoutUrl;
    }

    /**
     * Set the context key used to specify an alternate layout to be used
     * instead of the default layout. Screen content templates can override the
     * layout template that they wish to be wrapped with by setting this value
     * in the template, for example:<br>
     * <code>#set( $layout = "MyLayout.vm" )</code>
     * <p>
     * Default key is {@link #DEFAULT_LAYOUT_KEY "layout"}, as illustrated
     * above.
     * 
     * @param layoutKey
     *            the name of the key you wish to use in your screen content
     *            templates to override the layout template
     */
    public void setLayoutKey(String layoutKey) {
        this.layoutKey = layoutKey;
    }

    /**
     * Set the name of the context key that will hold the content of the screen
     * within the layout template. This key must be present in the layout
     * template for the current screen to be rendered.
     * <p>
     * Default is {@link #DEFAULT_SCREEN_CONTENT_KEY "screen_content"}: accessed
     * in VTL as <code>$screen_content</code>.
     * 
     * @param screenContentKey
     *            the name of the screen content key to use
     */
    public void setScreenContentKey(String screenContentKey) {
        this.screenContentKey = screenContentKey;
    }

    /**
     * Overrides <code>VelocityView.checkTemplate()</code> to additionally check
     * that both the layout template and the screen content template can be
     * loaded. Note that during rendering of the screen content, the layout
     * template can be changed which may invalidate any early checking done
     * here.
     */
    @Override
    public boolean checkResource(Locale locale) throws Exception {
        if (!super.checkResource(locale)) {
            return false;
        }

        try {
            // Check that we can get the template, even if we might subsequently
            // get it again.
            getTemplate(getTotalLayoutUrl(this.layoutUrl));
            return true;
        }
        catch (ResourceNotFoundException ex) {
            throw new NestedIOException(
                    "Cannot find Velocity template for URL ["
                            + this.layoutUrl
                            + "]: Did you specify the correct resource loader path?",
                    ex);
        }
        catch (Exception ex) {
            throw new NestedIOException(
                    "Could not load Velocity template for URL ["
                            + this.layoutUrl + "]", ex);
        }
    }

    /**
     * Overrides the normal rendering process in order to pre-process the
     * Context, merging it with the screen template into a single value
     * (identified by the value of screenContentKey). The layout template is
     * then merged with the modified Context in the super class.
     */
    @Override
    protected void doRender(Context context, HttpServletResponse response)
            throws Exception {
        renderScreenContent(context);

        // Velocity context now includes any mappings that were defined
        // (via #set) in screen content template.
        // The screen template can overrule the layout by doing
        // #set( $layout = "MyLayout.vm" )
        String layoutUrlToUse = (String) context.get(this.layoutKey);
        if (layoutUrlToUse != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Screen content template has requested layout ["
                        + layoutUrlToUse + "]");
            }
        }
        else {
            // No explicit layout URL given -> use default layout of this view.
            layoutUrlToUse = this.layoutDir + this.layoutUrl;
        }

        mergeTemplate(getTemplate(getTotalLayoutUrl(layoutUrlToUse)), context,
                response);
    }

    private String getTotalLayoutUrl(String layoutUrl) {
        return this.layoutDir + layoutUrl;
    }

    /**
     * The resulting context contains any mappings from render, plus screen
     * content.
     */
    private void renderScreenContent(Context velocityContext) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("Rendering screen content template [" + getUrl() + "]");
        }

        StringWriter sw = new StringWriter();
        Template screenContentTemplate = getTemplate(getUrl());
        screenContentTemplate.merge(velocityContext, sw);

        // Put rendered content into Velocity context.
        velocityContext.put(this.screenContentKey, sw.toString());
    }

    @Override
    protected Context createVelocityContext(Map model,
            HttpServletRequest request, HttpServletResponse response)
            throws IllegalStateException, IOException {
        ViewToolContext context = new ViewToolContext(getVelocityEngine(),
                request, response, getServletContext());

        context.putAll(model);

        if (null != getToolboxConfigLocation()
                || null != getToolboxConfigResource()) {
            XmlFactoryConfiguration cfg = new XmlFactoryConfiguration();

            URL url;
            if (null != getToolboxConfigLocation()) {
                url = new ServletContextResource(getServletContext(),
                        getToolboxConfigLocation()).getURL();
                cfg.read(url);
            }
            else if (null != getToolboxConfigResource()) {
                url = getToolboxConfigResource().getURL();
                cfg.read(url);
            }

            ToolboxFactory factory = cfg.createFactory();

            context.addToolbox(factory.createToolbox(Scope.APPLICATION));

            context.addToolbox(factory.createToolbox(Scope.REQUEST));

            context.addToolbox(factory.createToolbox(Scope.SESSION));
        }
        return context;
    }

    // @Override
    // protected Context createVelocityContext(Map<String, Object> model,
    // HttpServletRequest request,
    // HttpServletResponse response) throws Exception {// Create a
    // // ChainedContext
    // // instance.
    // ViewToolContext ctx;
    //
    // ctx = new ViewToolContext(getVelocityEngine(), request, response,
    // getServletContext());
    //
    // ctx.putAll(model);
    //
    // if (this.getToolboxConfigLocation() != null) {
    // ToolManager tm = new ToolManager();
    // tm.setVelocityEngine(getVelocityEngine());
    // tm.configure(getServletContext().getRealPath(getToolboxConfigLocation()));
    // if (tm.getToolboxFactory().hasTools(Scope.REQUEST)) {
    // ctx.addToolbox(tm.getToolboxFactory().createToolbox(Scope.REQUEST));
    // }
    // if (tm.getToolboxFactory().hasTools(Scope.APPLICATION)) {
    // ctx.addToolbox(tm.getToolboxFactory().createToolbox(Scope.APPLICATION));
    // }
    // if (tm.getToolboxFactory().hasTools(Scope.SESSION)) {
    // ctx.addToolbox(tm.getToolboxFactory().createToolbox(Scope.SESSION));
    // }
    // }
    // return ctx;
    // }

}
